package org.gendut.func;

// !Chunk for Lazy-Evalution
/* <literate> */
/**
 * This chunk holds and controls lazy-evaluated values. An instance of this
 * class is partially thread-safe: It is possible that multiple threads access a
 * lazy object concurrently, but for efficiency reasons, no provision is made
 * that prevents from evaluating the function more than once. However, a thread
 * won't reevaluate if it can see the result of a previous evaluation, and
 * processor caches will be synchronized in such way that only complete results
 * become visible. As an example, Concurrent Haskell has simililar behavior.
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
public class LazyValue<T> {
	/*
	 * Define a marker object for black holes. black holes are place holders for
	 * unevaluated objects.
	 */
	private static Object blackhole = new Object();

	/*
	 * An unevaluated lazy value consists of a function and the argument it
	 * applies to:
	 */
	private volatile Function f;

	private volatile Object arg;

	private volatile Throwable exception;

	/**
	 * The constructor takes a function and an argument. Both are applied when
	 * the value is accessed the first time.
	 */
	public <A> LazyValue(Function<? super A, ? extends T> f, A arg) {
		super();
		this.f = f;
		this.arg = arg;
	}

	/*
	 * We initialize the result as a black hole. The result is a <i>volatile</i>
	 * object in order to synchronize processor caches. Note that this is a
	 * result of Java's memory model and does not translate to C++, though
	 * similar keywords exist there.
	 */
	private volatile Object result = blackhole;

	/**
	 * Computes the value.
	 */
	final public T value() {
		if (result == blackhole)
			synchronized (this) {
				if (result == blackhole) {
					if (exception != null)
						doThrow(exception);
					Function fun = f;
					Object argument = arg;
					f = null;
					arg = null;
					try {
						result = fun.get(argument);
					} catch (Throwable e) {
						exception = e;
						doThrow(e);
					}
				}// `if`
			}// `synchronized`
		f = null;
		arg = null;
		return (T) result;
	}

	private static void doThrow(Throwable e) {
		if (e instanceof Error)
			throw (Error) e;
		else if (e instanceof RuntimeException)
			throw (RuntimeException) e;
		else
			throw new IllegalStateException("Unknown exception type", e);

	}

	/**
	 * Returns true if the value is still blackholed.
	 */
	final public boolean isBlackhole() {
		return result == blackhole;
	}
	/*
	 * Note: One might be tempted to use blackholing for detecting cycles during
	 * evaluation. This, however, does only work in single-threaded use.
	 */
}// `class`
